/*

 Copyright (C) 2007 Thomas Geiger (tom.geiger@gmail.com)

 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.

 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

 */

// Author: Thomas Geiger
// myProxy support by Manfred Gschweidl (manfred.gschweidl@students.fhv.at)

#include "CredManager.h"
#include "globus_libc.h"

namespace globus_common {

//used for password authentication
//Globus-C api wants a c-style funtion pointer => static memeber function
CredManager* __globalCallbackObjectGCM = NULL;

//password authentication
int CredManager::staticPWStdinCallback(char * buf, int num, int w) {
	int i;
	string pass = __globalCallbackObjectGCM->getKeyPassword();
	istringstream pwss(pass);
	pwss.getline(buf, num);
	i = strlen(buf);
	if (buf[i-1] == '\n') {
		buf[i-1] = '\0';
		i--;
	}

	return i;
}

//my proxy password authentication
int CredManager::readMyProxyPassphrase(char *buffer, const int buffer_len) {
	int i;
	string pass = this->getKeyPassword();
	istringstream pwss(pass);
	pwss.getline(buffer, buffer_len);
	i = strlen(buffer);
	if (buffer[i-1] == '\n') {
		buffer[i-1] = '\0';
		i--;
	}

	return i;
}

CredManager::CredManager() {
	globus_module_activate(GLOBUS_GSI_PROXY_MODULE);
	globus_module_activate(GLOBUS_GSI_CALLBACK_MODULE);
	globus_module_activate(GLOBUS_GSI_CERT_UTILS_MODULE);
	globus_module_activate(GLOBUS_GSI_GSS_ASSIST_MODULE);
	globus_module_activate(GLOBUS_GSI_CREDENTIAL_MODULE);

	proxy_handle = NULL;
	proxy_handle_attrs = NULL;
	callback_data = NULL;
	cred_handle_attrs = NULL;
	cred_handle = NULL;
	proxy_cred_handle = NULL;
	key_bits = 512; //key length
	valid = 12 * 60; //hours * minutes
	lifetime = 0;
	goodtill = 0;
	cert_type = GLOBUS_GSI_CERT_UTILS_TYPE_GSI_3_IMPERSONATION_PROXY;

	key_password = "";
	user_cert_filename = NULL;
	user_key_filename = NULL;
	proxy_out_filename = NULL;

	subject_name = NULL;
}

CredManager::~CredManager() {
	proxy_handle = NULL;
	proxy_handle_attrs = NULL;
	callback_data = NULL;
	cred_handle_attrs = NULL;
	cred_handle = NULL;
	proxy_cred_handle = NULL;

	user_cert_filename = NULL;
	user_key_filename = NULL;
	proxy_out_filename = NULL;

	subject_name = NULL;

	globus_module_deactivate(GLOBUS_GSI_GSS_ASSIST_MODULE);
	globus_module_deactivate(GLOBUS_GSI_CERT_UTILS_MODULE);
	globus_module_deactivate(GLOBUS_GSI_PROXY_MODULE);
	globus_module_deactivate(GLOBUS_GSI_CALLBACK_MODULE);
	globus_module_deactivate(GLOBUS_GSI_CREDENTIAL_MODULE);
}

void CredManager::destroyCredential() {
	major_status = globus_gsi_cred_handle_destroy(cred_handle);
	test_error(__LINE__);
}

void CredManager::destroyProxy() {
	major_status = globus_gsi_cred_handle_destroy(proxy_cred_handle);
	test_error(__LINE__);
	major_status = globus_gsi_proxy_handle_destroy(proxy_handle);
	test_error(__LINE__);
	major_status = globus_gsi_cred_handle_destroy(cred_handle);
	test_error(__LINE__);
	this->destroyCredentialHandle(cred_handle);
	this->destroyCredentialHandle(proxy_cred_handle);
}

void CredManager::readCredentialX509SubjectName() {
	major_status = globus_gsi_cred_get_X509_subject_name(cred_handle,
			&subject_name);
	test_error(__LINE__);
}

string CredManager::printCertificateSubject() {
	char* subject = NULL;
	char* issuer = NULL;
	time_t lifetime;

	//get user cert file system default / environment variables
	if (!this->getUserCertFile()) {
		return string();
	}
	//credential initialization
	if (!this->initCredential() ) {
		return string();
	}
	//read credential certificate
	if (!this->readCredentialCert() ) {
		return string();
	}

	major_status = globus_gsi_cred_get_subject_name(cred_handle, &subject);
	if (test_error(__LINE__)) {
		return string();
	}

	major_status = globus_gsi_cred_get_issuer_name(cred_handle, &issuer);
	if (test_error(__LINE__)) {
		return string();
	}

	major_status = globus_gsi_cred_get_lifetime(cred_handle, &lifetime);
	if (test_error(__LINE__)) {
		return string();
	}

	if (lifetime <= 0)
		lifetime = 0;

	long lifetime_days = 0;
	if (lifetime > 3600 * 24)
		lifetime_days = (long)((float)((lifetime / 3600) / 24.0));

	cout << "Subject: "<< subject << endl;
	cout << "Issuer: "<< issuer << endl;
	cout << "Valid for: "<< lifetime_days << " days"<< endl;

	return string(subject);
}

string CredManager::printProxySubject() {
	char* subject = NULL;
	char* issuer = NULL;
	char* identity = NULL;
	X509* proxy_cert = NULL;
	EVP_PKEY* pub_key = NULL;
	time_t lifetime;
	int strength = 0;

	getProxyOutFile();
	major_status = GLOBUS_GSI_SYSCONFIG_CHECK_KEYFILE(proxy_out_filename);
	if (test_error(__LINE__)) {
		return string();
	}

	major_status = globus_gsi_cred_handle_init(&proxy_cred_handle,
			cred_handle_attrs);
	test_error(__LINE__);
	major_status = globus_gsi_cred_read_proxy(proxy_cred_handle,
			proxy_out_filename);
	test_error(__LINE__);
	major_status = globus_gsi_cred_get_cert(proxy_cred_handle, &proxy_cert);
	test_error(__LINE__);
	if ((pub_key = X509_get_pubkey(proxy_cert)) == NULL) {
		cout << "error reading public key from proxy"<< endl;
		return string();
	}

	major_status
			= globus_gsi_cred_get_subject_name(proxy_cred_handle, &subject);
	if (test_error(__LINE__)) {
		return string();
	}

	major_status = globus_gsi_cred_get_issuer_name(proxy_cred_handle, &issuer);
	if (test_error(__LINE__)) {
		return string();
	}

	major_status = globus_gsi_cred_get_identity_name(proxy_cred_handle,
			&identity);
	if (test_error(__LINE__)) {
		return string();
	}

	/* type: restricted, limited or full */
	major_status = globus_gsi_cred_get_cert_type(proxy_cred_handle, &cert_type);
	if (test_error(__LINE__)) {
		return string();
	}

	major_status = globus_gsi_cred_get_lifetime(proxy_cred_handle, &lifetime);
	if (test_error(__LINE__)) {
		return string();
	}

	strength = 8 * EVP_PKEY_size(pub_key);
	//format lifetime
	if (lifetime <= 0)
		lifetime = 0;

	long lifetime_hour = (long)(lifetime / 3600);
	long lifetime_minutes = (long)(lifetime / 3600);
	long lifetime_seconds = (long)(lifetime % 60);
	long lifetime_days = 0;
	if (lifetime > 3600 * 24)
		lifetime_days = (long)((float)((lifetime / 3600) / 24.0));

	cout << "Subject: "<< subject << endl;
	cout << "Issuer: "<< subject << endl;
	cout << "Identity: "<< subject << endl;
	cout << "Type: "<< getCertificateType(cert_type) << endl;
	cout << "Strength: "<< strength << " bits"<< endl;
	cout << "Path: "<< proxy_out_filename << endl;
	cout << "Timeleft: "<< lifetime_hour << ":"<< lifetime_minutes << ":"
			<< lifetime_seconds;
	if (lifetime_days > 0)
		cout << " "<< lifetime_days << " days"<< endl;
	else
		cout << endl;
	if (subject != NULL)
		return string(subject);
	else
		return string();
}

string CredManager::getCertificateType(
		globus_gsi_cert_utils_cert_type_t cert_type) {
	string cert_type_name;
	switch (cert_type) {
	case GLOBUS_GSI_CERT_UTILS_TYPE_RFC_IMPERSONATION_PROXY:
		cert_type_name = "RFC 3820 compliant impersonation proxy";
		break;
	case GLOBUS_GSI_CERT_UTILS_TYPE_RFC_INDEPENDENT_PROXY:
		cert_type_name = "RFC 3820 compliant independent proxy";
		break;
	case GLOBUS_GSI_CERT_UTILS_TYPE_RFC_LIMITED_PROXY:
		cert_type_name = "RFC 3820 compliant limited proxy";
		break;
	case GLOBUS_GSI_CERT_UTILS_TYPE_RFC_RESTRICTED_PROXY:
		cert_type_name = "RFC 3820 compliant restricted proxy";
		break;
	case GLOBUS_GSI_CERT_UTILS_TYPE_GSI_3_IMPERSONATION_PROXY:
		cert_type_name = "Proxy draft (pre-RFC) compliant impersonation proxy";
		break;
	case GLOBUS_GSI_CERT_UTILS_TYPE_GSI_3_INDEPENDENT_PROXY:
		cert_type_name = "Proxy draft (pre-RFC) compliant independent proxy";
		break;
	case GLOBUS_GSI_CERT_UTILS_TYPE_GSI_3_LIMITED_PROXY:
		cert_type_name = "Proxy draft (pre-RFC) compliant limited proxy";
		break;
	case GLOBUS_GSI_CERT_UTILS_TYPE_GSI_3_RESTRICTED_PROXY:
		cert_type_name = "Proxy draft (pre-RFC) compliant restricted proxy";
		break;
	case GLOBUS_GSI_CERT_UTILS_TYPE_GSI_2_PROXY:
		cert_type_name = "full legacy globus proxy";
		break;
	case GLOBUS_GSI_CERT_UTILS_TYPE_GSI_2_LIMITED_PROXY:
		cert_type_name = "limited legacy globus proxy";
		break;
	case GLOBUS_GSI_CERT_UTILS_TYPE_EEC:
		cert_type_name = "end entity credential";
		break;
	default:
		cout << "error: not a proxy"<< endl;
		return string();
	}
	return cert_type_name;
}

//needs to be bool
bool CredManager::destroyProxyFile() {
	this->getProxyOutFile();
	remove(proxy_out_filename);
	if (!this->hasCredential()) {
		return true;
	} else {
		return false;
	}
}

bool CredManager::initCredentialAttributes() {
	major_status = globus_gsi_cred_handle_attrs_init(&cred_handle_attrs);
	return !test_error(__LINE__);
}

bool CredManager::destroyCredentialAttributes() {
	major_status = globus_gsi_cred_handle_attrs_destroy(cred_handle_attrs);
	return !test_error(__LINE__);
}

bool CredManager::initCredential() {
	//credential attributes intialization
	if (!this-> initCredentialAttributes() ) {
		return false;
	}
	//init credential
	major_status = globus_gsi_cred_handle_init(&cred_handle, cred_handle_attrs);
	if (major_status != GSS_S_COMPLETE) {
		globus_gss_assist_display_status(stdout,
				"Coudl not inizialize credential.", major_status, minor_status,
				0);
		return false;
	}
	//destroy credentail Attributes
	if (!this->destroyCredentialAttributes() ) {
		return false;
	}
	return true;
}

void CredManager::destroyCredentialHandle(globus_gsi_cred_handle_t handle) {
	major_status = globus_gsi_cred_handle_destroy(handle);
	test_error(__LINE__);
}

bool CredManager::readCredentialCert() {
	if (user_cert_filename == NULL) {
		cout << "No user certificate found." << endl;
		return false;
	}
	
	major_status = globus_gsi_cred_read_cert(cred_handle, user_cert_filename);
	if (major_status != GSS_S_COMPLETE) {
		string msg = "Could not find a certificate at "
				+ string(user_cert_filename) + ".";
		globus_gss_assist_display_status(stdout, (char*) msg.c_str(),
				major_status, minor_status, 0);
		return false;
	}
	return true;
}

bool CredManager::readCredentialKey() {
	__globalCallbackObjectGCM = this;

	int (*fp_pw)() = NULL;
	fp_pw = (int(*)())CredManager::staticPWStdinCallback;
	major_status = globus_gsi_cred_read_key(cred_handle, user_key_filename,
			fp_pw);
	//todo: check error if key was sealed with passphrase and openssl error
	__globalCallbackObjectGCM = NULL;
	if (major_status != GSS_S_COMPLETE) {
		globus_gss_assist_display_status(stdout,
				"Could not read credential key.", major_status, minor_status, 0);
		return false;
	}
	return true;
}

void CredManager::createProxy() {
	major_status = globus_gsi_proxy_create_signed(proxy_handle, cred_handle,
			&proxy_cred_handle);
	test_error(__LINE__);
}

void CredManager::verifyProxy() {
	major_status = globus_gsi_cred_verify(proxy_cred_handle);
	test_error(__LINE__);
}

void CredManager::writeProxy() {
	cout << "Created proxy: "<< proxy_out_filename << endl;
	major_status = globus_gsi_cred_write_proxy(proxy_cred_handle,
			proxy_out_filename);
	test_error(__LINE__);
}

bool CredManager::hasCredential() {
	major_status = globus_gss_assist_acquire_cred(&minor_status, GSS_C_BOTH,
			&handle);

	if (major_status == GSS_S_COMPLETE) {
		return true;
	}

	return hasUserProxy();
}

bool CredManager::hasUserProxy() {

	char* proxy = globus_libc_getenv("X509_USER_PROXY");
	if (proxy != NULL) {

		globus_gsi_cred_handle_t temp_handle;
		globus_gsi_cred_handle_attrs_t temp_handle_attrs;

		globus_gsi_cred_handle_attrs_init(&temp_handle_attrs);
		globus_gsi_cred_handle_init(&temp_handle, temp_handle_attrs);

		globus_gsi_cred_read_proxy(temp_handle, proxy);

		major_status = globus_gsi_cred_verify(temp_handle);

		if (major_status != GLOBUS_SUCCESS) {
			globus_error_print_friendly( globus_error_peek(major_status));
		} else {
			cout << "Detected user proxy."<< endl;
			return true;
		}
	}
	return false;
}

void CredManager::readCredentialLifetime() {
	major_status = globus_gsi_cred_get_lifetime(cred_handle, &lifetime);
	test_error(__LINE__);
}

void CredManager::readCredentialGoodTill() {
	if (proxy_cred_handle == NULL) {
		major_status = globus_gsi_cred_get_goodtill(cred_handle, &goodtill);
	} else {
		major_status = globus_gsi_cred_get_goodtill(proxy_cred_handle,
				&goodtill);
	}
	test_error(__LINE__);
}

void CredManager::initProxyAttributes() {
	//init proxy attrs
	major_status = globus_gsi_proxy_handle_attrs_init(&proxy_handle_attrs);
	test_error(__LINE__);

	major_status = globus_gsi_proxy_handle_attrs_set_keybits(
			proxy_handle_attrs, key_bits);
	test_error(__LINE__);
}

void CredManager::destroyProxyAttributes() {
	major_status = globus_gsi_proxy_handle_attrs_destroy(proxy_handle_attrs);
	test_error(__LINE__);
}

void CredManager::setProxyCertificateLifetime() {
	major_status = globus_gsi_proxy_handle_set_time_valid(proxy_handle, valid);
	test_error(__LINE__);
}

void CredManager::setProxyCertificateType() {
	major_status = globus_gsi_proxy_handle_set_type(proxy_handle, cert_type);
	test_error(__LINE__);
}

void CredManager::initProxy() {
	major_status = globus_gsi_proxy_handle_init(&proxy_handle,
			proxy_handle_attrs);
	test_error(__LINE__);
}

bool CredManager::acquireCredential(string pass) {
	this->trimSpaces(pass);
	this->key_password = pass;
	//init proxy attrs
	this->initProxyAttributes();
	//init proxy
	this->initProxy();
	//detroy proxy attrs
	this->destroyProxyAttributes();
	//get user cert file system default / environment variables
	if (!this->getUserCertFile()) {
		return false;
	}
	//get proxy out file system default and check directory
	this->getProxyOutFile();
	//set proxy lifetime
	this->setProxyCertificateLifetime();
	//set proxy type
	this->setProxyCertificateType();
	//credential initialization
	if (!this->initCredential()) {
		return false;;
	}
	//read credential certificate
	if (!this->readCredentialCert()) {
		return false;
	}
	//read private key
	this->readCredentialKey();
	//now ready for proxy action
	this->createProxy();
	//verify proxy
	this->verifyProxy();
	//write proxy
	this->writeProxy();
	//check lifetime of proxy certificate
	this->readCredentialLifetime();
	return true;
}

bool CredManager::getUserCertFile() {

	major_status = GLOBUS_GSI_SYSCONFIG_GET_USER_CERT_FILENAME(
			&user_cert_filename, &user_key_filename);
	//cout << major_status << ", " << user_cert_filename << ", " << user_key_filename << endl;

	return true;
}

void CredManager::getProxyOutFile() {
	char* proxy_absolute_path;
	char* temp_dir;
	char* temp_filename;

	if (proxy_out_filename == NULL) {
		major_status = GLOBUS_GSI_SYSCONFIG_GET_PROXY_FILENAME(
				&proxy_out_filename, GLOBUS_PROXY_FILE_OUTPUT);
		if (major_status != GLOBUS_SUCCESS) {
			cerr
					<< "\n\nERROR: Couldn't find a valid location to write the proxy file\n";
		}
	}
	major_status = GLOBUS_GSI_SYSCONFIG_MAKE_ABSOLUTE_PATH_FOR_FILENAME(
			proxy_out_filename, &proxy_absolute_path);
	proxy_out_filename = proxy_absolute_path;
	major_status = GLOBUS_GSI_SYSCONFIG_SPLIT_DIR_AND_FILENAME(
			proxy_absolute_path, &temp_dir, &temp_filename);
	major_status = GLOBUS_GSI_SYSCONFIG_DIR_EXISTS(temp_dir);
	test_error(__LINE__);
	//true proxy_out_filename could be what it is
	//return true;

}

bool CredManager::releaseCredential() {
	return this->destroyProxyFile();
}

string CredManager::getKeyPassword() {
	return this->key_password;
}

bool CredManager::acquireCredentialMyProxy(string myproxyServer = "",
		string pass = "") {
	myproxy_socket_attrs_t *socket_attrs = new myproxy_socket_attrs_t;
	myproxy_request_t *client_request = new myproxy_request_t;
	myproxy_response_t *server_response = new myproxy_response_t;
	int return_value = 1;

	int dn_as_username = 0;

	/* check library version */
	/*
	 if (myproxy_check_version()) {
	 fprintf(stderr, "MyProxy library version mismatch.\nExpecting %s.  Found %s.\n",
	 MYPROXY_VERSION_DATE, myproxy_version(0,0,0));
	 exit(1);
	 }
	 */
	myproxy_log_use_stream (stderr);

	setvbuf((stdout), (char *) NULL, _IOLBF, 0);
	setvbuf((stderr), (char *) NULL, _IOLBF, 0);

	memset(socket_attrs, 0, sizeof(*socket_attrs));

	memset(client_request, 0, sizeof(*client_request));

	memset(server_response, 0, sizeof(*server_response));

	/* Setup defaults */
	myproxy_set_delegation_defaults(socket_attrs,client_request);

	/* Initialize client arguments and create client request object */
	this->trimSpaces(myproxyServer);
	if(!myproxyServer.empty()) {
		socket_attrs->pshost = (char*)myproxyServer.c_str();
	}

	/* Check to see if myproxy-server specified */
	if (socket_attrs->pshost == NULL) {
		fprintf(stderr,"Unspecified myproxy-server. Please set the MYPROXY_SERVER environment variable\nor set the myproxy-server hostname via the -s flag.\n");
		exit(1);
    }

    /* Connect to server. */
    if (myproxy_init_client(socket_attrs) < 0) {
        verror_print_error(stderr);
        goto cleanup;
    }
    
    if(proxy_out_filename != NULL) {
    	string tmpProxyFilename(proxy_out_filename);
    	this->trimSpaces(tmpProxyFilename);
    	if(tmpProxyFilename.empty()) {
			this->getProxyOutFile();
    	} else {
    		proxy_out_filename = (char*)tmpProxyFilename.c_str();
    	}
    } else {
		this->getProxyOutFile();    	
    }

	int rval;
	if(pass.size() > 0) {
		trimSpaces(pass);
	} else {
		pass = "";
	}

	if( !pass.empty() ) {
		/* Allow user to provide a passphrase */
		this->key_password = pass;	
		rval = this->readMyProxyPassphrase(client_request->passphrase, sizeof(client_request->passphrase));		
	} else {
	    rval = myproxy_read_passphrase(client_request->passphrase,
					   sizeof(client_request->passphrase),
					   NULL);
	}

	if (rval == -1) {
	    verror_print_error(stderr);
	    goto cleanup;
    }

   if (client_request->username == NULL) { /* set default username */
		if (dn_as_username) {
		    if (client_request->authzcreds) {
	
		    	// TODO
		    	/*
		    	if (ssl_get_base_subject_file(client_request->authzcreds,
						      &client_request->username)) {
			    fprintf(stderr, "Cannot get subject name from %s.\n",
				    client_request->authzcreds);
			    goto cleanup;
			    
		    	}
		    	*/
		    } else {
		    
		    	// TODO
		    	/*
		    	if (ssl_get_base_subject_file(NULL,
						      &client_request->username)) {
		    		fprintf(stderr,
				    	"Cannot get subject name from your certificate.\n");
		    		goto cleanup;
		    	}
		    	*/
		    }
		} else {
		    char *username = NULL;
		    if (!(username = getenv("LOGNAME"))) {
			fprintf(stderr, "Please specify a username.\n");
			goto cleanup;
		    }
		    client_request->username = strdup(username);
		}
    }

    if (myproxy_get_delegation(socket_attrs, client_request, NULL,
			       server_response, proxy_out_filename)!=0) {
		fprintf(stderr, "Failed to receive credentials.\n");
		verror_print_error(stderr);
		goto cleanup;
    }
    
    cout << "Created proxy: " << proxy_out_filename << endl;
    //return_value = 0;
    return true;

	cleanup:
		/* free memory allocated */
		delete socket_attrs;
		delete client_request;
		delete server_response;
		//return return_value;
		return false;
}

void CredManager::trimSpaces( string& str)
{
    // Trim Both leading and trailing spaces
	size_t startpos = str.find_first_not_of(" "); // Find the first character position after excluding leading blank spaces
	size_t endpos = str.find_last_not_of(" "); // Find the first character position from reverse af
    // if all spaces or empty return an empty string
	if(( string::npos == startpos ) || ( string::npos == endpos))
	{
		str = "";
	} else {
		str = str.substr( startpos, endpos-startpos+1 );
	}
}

bool CredManager::test_error(int line)
{
    if(major_status == GLOBUS_SUCCESS)
    {
        return false;
    }

	cerr << "Code line: " << line << ", ";
	cerr << "ERROR: " << globus_error_print_friendly( globus_error_peek(major_status) ) << endl;

    //globus_assert(0);
    return true;
}

bool CredManager::acquireCredential(gss_cred_id_t& cred) {
	OM_uint32 major_status;
	OM_uint32 minor_status;
	
	major_status = globus_gss_assist_acquire_cred(&minor_status,
	    		                				GSS_C_BOTH,
	    		                				&cred);
	
    if ( major_status != GSS_S_COMPLETE )
    {
    	return false;
    }
    return true;
}

bool CredManager::releaseCredential(gss_cred_id_t& cred) {
	OM_uint32 major_status;
	OM_uint32 minor_status;
	major_status = gss_release_cred( &minor_status, &cred);
	    
	if ( major_status != GSS_S_COMPLETE )
	{
		return false;
	}
	return true;
}

}
